package com.gframework.boot.flyway;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;

import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.DefaultApplicationArguments;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.event.EventPublishingRunListener;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.objenesis.ObjenesisStd;
import org.springframework.util.StringUtils;

/**
 * 本类可以实现不启动springboot容器来执行flyway.
 * <p>这主要是用于开发环境下，避免每次需要启动项目来初始化库的烦恼。
 * <p>本类也会读取application配置文件去初始化Environment，因此你只需要按照正常的spring配置去配置即可，无需任何其他复杂操作。
 * @since 2.0.0
 * @author Ghwolf
 *
 */
public class FlywayDevRunner {
	
	private FlywayDevRunner(){}

	/**
	 * 执行flyway的migrate操作
	 */
	public static void migrate() throws Exception {
		if (!StringUtils.isEmpty(System.getProperty("PID"))) {
			System.err.println("请不要再springboot环境下使用 FlywayDevRunner 类，这仅仅是用于开发阶段的一个快捷使用flyway的工具类。");
			return ;
		}
		
		FlywayDevRunner f = new FlywayDevRunner();
		ConfigurableEnvironment env = f.getEnvironment();
		FlywaySpringRunListener r = new FlywaySpringRunListener(true);
		r.environmentPrepared(env);
	} 
	
	/**
	 * 获取springboot环境配置对象
	 */
	private ConfigurableEnvironment getEnvironment() throws Exception{
		StackTraceElement[] stackTraces = new Exception().getStackTrace();
		
		ApplicationArguments applicationArguments = new DefaultApplicationArguments();
		ObjenesisStd std = new ObjenesisStd();
		Object listeners = std.newInstance(Class.forName("org.springframework.boot.SpringApplicationRunListeners"));
		Field listenersField = listeners.getClass().getDeclaredField("listeners");
		listenersField.setAccessible(true);
		
		SpringApplication app = new SpringApplication(Class.forName(stackTraces[stackTraces.length - 1].getClassName()));
		listenersField.set(listeners, Arrays.asList(new EventPublishingRunListener(app,new String[0])));
		
		Method prepareEnvironmentMethod = SpringApplication.class.getDeclaredMethod("prepareEnvironment", listeners.getClass(),ApplicationArguments.class);
		prepareEnvironmentMethod.setAccessible(true);
		return (ConfigurableEnvironment)prepareEnvironmentMethod.invoke(app, listeners,applicationArguments);
	}
	
}
